package Func;

import java.io.IOException;
import java.util.*;

import Kit.FileIO;
import Object.Rating;
import Object.Movie;
import Func.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Map.Entry;

import static Func.Goble.*;

public class GobleKit
{
    public static void initMovies(ArrayList<String> arrayList,boolean createCache) throws JsonProcessingException
    {
        if (isDebug)
            GobleTool.printInfo("initMovies");
        for (int i=0;i<arrayList.size();i++)
        {
            String thisMovie = arrayList.get(i);
            String thisInfo[] = thisMovie.split("::");
            String tags[] = thisInfo[2].split("\\|");
            Movie movie = new Movie(Integer.parseInt(thisInfo[0]),thisInfo[1],tags);
            movieList.put(movie.getMovieId(),movie);
            if (isDebug)
                if (i%1000==0)
                    System.out.print("="+i/100+"%"+"=");
        }
        System.out.println();

        if (createCache)
        {
            if (isDebug)
                GobleTool.printInfo("正在重新创建movieList缓存");
            FileIO io = new FileIO();
            String path="d:/teamdata/cache/system/movieList.dat";
            io.deleteFile(path);
            ObjectMapper objectMapper = new ObjectMapper();
            for (Integer thisKey:movieList.keySet())
                io.writeFile(path,thisKey + "::" + objectMapper.writeValueAsString(movieList.get(thisKey)));
        }
    }

    public static void initRatings(ArrayList<String> arrayList) throws JsonProcessingException
    {
        if (isDebug)
            GobleTool.printInfo("initRatings");
        for (int i=0;i<arrayList.size();i++)
        {
            String thisRating = arrayList.get(i);

            String thisInfo[] = thisRating.split("::");
            Rating rating = new Rating(Integer.parseInt(thisInfo[0]),
                                        Integer.parseInt(thisInfo[1]),
                                        Float.parseFloat(thisInfo[2]),
                                        thisInfo[3]);
            ratingList.add(rating);
            if (isDebug)
                if (i%900000==0)
                    System.out.print("="+i/90000+"%"+"=");
        }
        System.out.println();
    }

    public static void initMapping(boolean createCache) throws IOException
    {
        if (isDebug)
            GobleTool.printInfo("initMapping");
        int count = 0;
        for (int i=0;i<ratingList.size();i++)
        {
            Rating thisRating = ratingList.get(i);
            int thisMovieId = thisRating.getMovieId();
            int thisUserId = thisRating.getUserId();

            if (item_to_user.get(thisMovieId)==null)
            {
                HashMap<Integer,Float> tempMap = new HashMap<Integer, Float>();
                tempMap.put(thisUserId,thisRating.getRating());
                item_to_user.put(thisMovieId,tempMap);
            }
            else
            {
                item_to_user.get(thisMovieId).put(thisUserId,thisRating.getRating());
            }

            if (user_to_item.get(thisUserId)==null)
            {
                HashMap<Integer,Float> tempMap = new HashMap<Integer, Float>();
                tempMap.put(thisMovieId,thisRating.getRating());
                user_to_item.put(thisUserId,tempMap);
            }
            else
            {
                user_to_item.get(thisUserId).put(thisMovieId,thisRating.getRating());
            }
            count++;
            if (isDebug)
                if (count%900000==0)
                    System.out.print("="+count/90000+"%"+"=");
        }
        System.out.println();

        if (createCache)
        {
            if (isDebug)
                GobleTool.printInfo("正在重新创建Mapping缓存");
            FileIO io = new FileIO();
            String path1="d:/teamdata/cache/system/user_to_item";
            String path2="d:/teamdata/cache/system/item_to_user";
            List<LinkedHashMap<Integer, HashMap<Integer, Float>>> list1 = GobleTool.mapChunk(new LinkedHashMap<>(user_to_item),5000);
            List<LinkedHashMap<Integer, HashMap<Integer, Float>>> list2 = GobleTool.mapChunk(new LinkedHashMap<>(item_to_user),1000);

            ObjectMapper objectMapper = new ObjectMapper();
            for (int i=0;i<list1.size();i++)
            {
                io.deleteFile(path1+i+".dat");
                for (Integer thisKey:list1.get(i).keySet())
                    io.writeFile(path1+i+".dat",thisKey + "::" + objectMapper.writeValueAsString(list1.get(i).get(thisKey)));
            }

            for (int i=0;i<list2.size();i++)
            {
                io.deleteFile(path2+i+".dat");
                for (Integer thisKey:list2.get(i).keySet())
                    io.writeFile(path2+i+".dat",thisKey + "::" + objectMapper.writeValueAsString(list2.get(i).get(thisKey)));
            }
        }
    }

    public static ArrayList<String> readSimilarityCache(int movieId) throws IOException
    {
        if (isDebug)
            GobleTool.printInfo("正在查询缓存文件是否存在");

        FileIO io = new FileIO();
        ArrayList<String> cacheContent = io.readFile("d:/teamdata/cache/"+ ((movieId/500)+1) +"/movies_"+movieId+".dat");
        if (cacheContent!=null)
            return cacheContent;
        else
        {
            GobleTool.printInfo("缓存不存在，正在处理推送计算任务");
            ArrayList<String> newCacheContent = GobleKit.similarity(movieId);
            return newCacheContent;
        }
    }

    public static ArrayList<String> similarity(int movieId) throws IOException
    {
        if (item_to_user.size()==0 || user_to_item.size()==0 ||movieList.size()==0)
        {
            GobleTool.printWaitInfo("未读入数据，请执行读取操作或加载缓存");
            return null;
        }
        long startTime =  System.currentTimeMillis();

        HashMap<Integer,Double> resultWithoutSort = new HashMap<>();
        HashMap<Integer,Float> ratingUserIds = item_to_user.get(movieId);
        HashSet<Integer> readyComMovie = new HashSet<>();//待比较的电影ID
        //先找出和哪些电影比较
            //找出评论此电影的用户
            //找出这些用户所评论过的电影

        if (ratingUserIds!=null && ratingUserIds.size()!=0)
        {
            for (int userId:ratingUserIds.keySet())
            {
                HashMap<Integer,Float> userRatingMovies = user_to_item.get(userId);
                readyComMovie.addAll(userRatingMovies.keySet());
            }

            readyComMovie.remove(movieId);
            //再计算本电影与这些电影的相似度
            for (int compMovieId:readyComMovie)
            {
                double fenzi = 0;
                double fenmu_front = 0;
                double fenmu_rear = 0;

                //影响运行效率，已经改进
/*                System.out.println(1);
                HashMap<Integer,Rating> userI = item_to_user.get(movieId);
                HashMap<Integer,Rating> userIcopy = new HashMap<Integer, Rating>(userI);
                System.out.println(2);*/

                double tempResult;
                HashMap<Integer,Float> userJ = item_to_user.get(compMovieId);

                //TODU:优化,小量集合去重大量集合速度快
                if(userJ.size()>=100 && ratingUserIds.size()>=100)
                {
                    Set<Integer> userList;
                    if (userJ.size()<ratingUserIds.size())
                    {
                        userList = new HashSet<>(userJ.keySet());
                        userList.retainAll(ratingUserIds.keySet());
                    }
                    else
                    {
                        userList = new HashSet<>(ratingUserIds.keySet());
                        userList.retainAll(userJ.keySet());
                    }

                    if(userList.size()>=100)
                    {
                        for (Integer userId : userList)
                        {
                            HashMap<Integer,Float> u = user_to_item.get(userId);
                            double tempN = u.get(movieId);
                            double tempM = u.get(compMovieId);
                            fenzi += tempN * tempM;
                            fenmu_front += tempN*tempN;
                            fenmu_rear += tempM*tempM;
                        }
                        fenmu_front = Math.sqrt(fenmu_front);
                        fenmu_rear = Math.sqrt(fenmu_rear);

                        tempResult = fenzi / (fenmu_front * fenmu_rear);
                    }
                    else
                        tempResult=0;
                }
                else
                    tempResult=0;
                resultWithoutSort.put(compMovieId,tempResult);
            }
        }

        resultWithoutSort.remove(movieId);

        List<Entry<Integer,Double>> list = new ArrayList<>(resultWithoutSort.entrySet());
        Collections.sort(list, Comparator.comparing(Entry::getValue));
        Collections.reverse(list);
        List<Entry<Integer,Double>> resultList;

        if (list.size()>=15)
            resultList = list.subList(0,15);
        else
            resultList = list.subList(0,list.size());
        //封装结果集合
        ArrayList<String> resultArray = new ArrayList<>();
        for (int i=0;i<resultList.size();i++)
        {
            resultArray.add((i+1)+":"+Goble.movieList.get(resultList.get(i).getKey())+":"+resultList.get(i).getValue());
        }
        //写入到缓存文件
        FileIO io = new FileIO();
        io.writeFile("d:/teamdata/cache/"+ ((movieId/500)+1) +"/movies_"+movieId+".dat",resultArray);
        long endTime =  System.currentTimeMillis();
        double usedTime = (endTime-startTime)/1000.0;

        ObjectMapper objectMapper = new ObjectMapper();
        String path="d:/teamdata/cache/endingResult.dat";
        io.writeFile(path,movieId+"::"+objectMapper.writeValueAsString(resultList));

        GobleTool.printInfo("本次处理用时:"+usedTime+"秒");
        if (isDebug)
        {
            //System.out.println(ttTime1);
            //System.out.println(ttTime2);
        }
        return resultArray;
    }
}
